/*
* ***************************************************************************
* Copyright (C) 2016 Marvell International Ltd.
* ***************************************************************************
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of Marvell nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
***************************************************************************
*/

#include <plat_def.h>
#include <mmio.h>
#include <icu.h>

#define ICU_SET_SPI_AL(x)	(0x10 + (0x10 * x))
#define ICU_SET_SPI_AH(x)	(0x14 + (0x10 * x))
#define ICU_CLR_SPI_AL(x)	(0x18 + (0x10 * x))
#define ICU_CLR_SPI_AH(x)	(0x1c + (0x10 * x))
#define ICU_INT_CFG(x)		(0x100 + 4 * x)

#define ICU_INT_ENABLE_OFFSET	(24)
#define ICU_IS_EDGE_OFFSET	(28)
#define ICU_GROUP_OFFSET	(29)

enum icu_group {
	ICU_GRP_NSR	= 0,
	ICU_GRP_SR	= 1,
	ICU_GRP_LPI	= 2,
	ICU_GRP_VLPI	= 3,
	ICU_GRP_SEI	= 4,
	ICU_GRP_REI	= 5,
	ICU_GRP_MAX,
};

struct icu_msi {
	enum icu_group group;
	uintptr_t set_spi_addr;
	uintptr_t clr_spi_addr;
};

#define MAX_ICU_IRQS		207

/* Allocate the MSI address per interrupt group,
 * unsopprted groups get NULL address */
static struct icu_msi msi_addr[] = {
	{ICU_GRP_NSR,  0xf03f0040, 0xf03f0048}, /* Non secure interrupts*/
	{ICU_GRP_SR,   0,	   0x0},	/* Secure interrupts */
	{ICU_GRP_LPI,  0x0,	   0x0},	/* LPI interrupts */
	{ICU_GRP_VLPI, 0x0,	   0x0},	/* Virtual LPI interrupts */
	{ICU_GRP_SEI,  0xf03f0230, 0xf03f0230},	/* System error interrupts */
	{ICU_GRP_REI,  0xf03f0270, 0xf03f0270},	/* RAM error interrupts */
};

static void icu_clear_irq(uintptr_t icu_base, int nr)
{
	mmio_write_32(icu_base + ICU_INT_CFG(nr), 0);
}

static void icu_set_irq(uintptr_t icu_base, const struct icu_irq *irq,
			uint32_t spi_base, enum icu_group group)
{
	uint32_t icu_int;

	icu_int  = (irq->spi_id + spi_base) | (1 << ICU_INT_ENABLE_OFFSET);
	icu_int |= irq->is_edge << ICU_IS_EDGE_OFFSET;
	icu_int |= group << ICU_GROUP_OFFSET;

	mmio_write_32(icu_base + ICU_INT_CFG(irq->icu_id), icu_int);
}

/*
 *  This function uses 2 spi values to initialize the ICU
 *  spi_base: used to set the base of the SPI id in the MSI message
 *            generated by the ICU. AP806-Z1 required spi_base=64 while
 *            AP806-A0 uses spi_base=0
 *  spi_offset: used to shift the multi instance interrupts between CP-0
 *              and CP-1
 */
void icu_init(int cp_index, int spi_base, int spi_offset,
	const struct icu_config *config)
{
	int i;
	const struct icu_irq *irq;
	struct icu_msi *msi;
	uintptr_t icu_base = MVEBU_ICU_REG_BASE(cp_index);

	/* Set the addres for SET_SPI and CLR_SPI registers in AP */
	msi = msi_addr;
	for (i = 0; i < ICU_GRP_MAX; i++, msi++) {
		mmio_write_32(icu_base + ICU_SET_SPI_AL(msi->group), msi->set_spi_addr & 0xFFFFFFFF);
		mmio_write_32(icu_base + ICU_SET_SPI_AH(msi->group), msi->set_spi_addr >> 32);
		mmio_write_32(icu_base + ICU_CLR_SPI_AL(msi->group), msi->clr_spi_addr & 0xFFFFFFFF);
		mmio_write_32(icu_base + ICU_CLR_SPI_AH(msi->group), msi->clr_spi_addr >> 32);
	}

	/* Mask all ICU interrupts */
	for (i = 0; i < MAX_ICU_IRQS; i++)
		icu_clear_irq(icu_base, i);

	/* Configure the ICU interrupt lines */
	/* Multi instance interrupts use different SPI ID for CP-1*/
	irq = config->ns_multi.map;
	for (i = 0; i < config->ns_multi.size; i++, irq++)
		icu_set_irq(icu_base, irq, spi_base + spi_offset, ICU_GRP_NSR);

	irq = config->ns_single.map;
	for (i = 0; i < config->ns_single.size; i++, irq++)
		icu_set_irq(icu_base, irq, spi_base, ICU_GRP_NSR);

	irq = config->sei.map;
	for (i = 0; i < config->sei.size; i++, irq++)
		icu_set_irq(icu_base, irq, spi_base, ICU_GRP_SEI);

	irq = config->rei.map;
	for (i = 0; i < config->rei.size; i++, irq++)
		icu_set_irq(icu_base, irq, spi_base, ICU_GRP_REI);

	return;
}
